
#include "can.hxx"
#include "http.hxx"
#include "sock-inet-tcp.hxx"

#include <cstdlib>
#include <functional>
#include <iostream>
#include <list>
#include <map>
#include <mutex>
#include <vector>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>

using std::cerr;
using std::endl;
using std::exception;
using std::function;
using std::istringstream;
using std::list;
using std::lock_guard;
using std::map;
using std::mutex;
using std::ostringstream;
using std::ref;
using std::set;
using std::string;
using std::thread;
using std::vector;

////////////////////////////////////////////////////////////////////////////////

template <typename T> string print(const T& t) {
    ostringstream os;
    os << t;
    return os.str();
}

template <> string print<vector<char>>(const vector<char>& t) {
    return string(t.begin(), t.end());
}

template <typename T> T& operator>>(const string& s, T& t) {
    istringstream(s) >> t;
    return t;
}

template <typename T> T& operator>>(const vector<char>& v, T& t) {
    return (print(v) >> t);
}

////////////////////////////////////////////////////////////////////////////////

int main(int argc, const char* argv[]) {
    uint16_t service = 2611;

    if(argc == 2)
    {
        string(argv[1]) >> service;
    }

    try {
        auto server = sock_inet_tcp::listen(sock_inet::address(0, service));
        map<string,function<void(sock_inet_tcp&,const map<string,string>&)>> locations;
        map<int,string> peers;
        can network;
        mutex resources_mutex;
        auto crlf = "\r\n";

        locations["/"] = [&](sock_inet_tcp& client, const map<string,string>& headers) {
            uint16_t port = 0;

            if(!(http::read_body(client, headers) >> port)) {
                throw http::bad_request_error();
            }

            {
                lock_guard<mutex> resources_guard(resources_mutex);
                peers[client.get_fd()] = sock_inet::address(client.get_remote().get_host(), port);
            }

            client <<
                "HTTP/1.1 200 OK" << crlf <<
                "Connection: Keep-Alive" << crlf <<
                "Content-Type: application/json" << crlf <<
                "Content-Length: 33" << crlf <<
                "" << crlf <<
                "Welcome! I am simple-file-server." <<
                ostream::flush;
        };

        locations["/put-files"] = [&](sock_inet_tcp& client, const map<string,string>& headers) {
            istringstream is(print(http::read_body(client, headers)));
            set<string> files;
            for(string file; is >> file; files.insert(file));

            {
                lock_guard<mutex> resources_guard(resources_mutex);
                network.erase(peers[client.get_fd()]);
                network.insert(peers[client.get_fd()], files.begin(), files.end());
            }

            client <<
                "HTTP/1.1 200 OK" << crlf <<
                "Connection: Keep-Alive" << crlf <<
                "" << crlf <<
                ostream::flush;
        };

        locations["/get-files"] = [&](sock_inet_tcp& client, const map<string,string>& headers) {
            http::skip_body(client, headers);

            string files;

            {
                lock_guard<mutex> resources_guard(resources_mutex);
                for(auto& file : network.get_files()) {
                    files.append(file).append(crlf);
                }
            }

            client <<
                "HTTP/1.1 200 OK" << crlf <<
                "Connection: Keep-Alive" << crlf <<
                "Content-Length: " << files.length() << crlf <<
                "" << crlf <<
                files <<
                ostream::flush;
        };

        locations["/get-owners"] = [&](sock_inet_tcp& client, const map<string,string>& headers) {
            string file;

            if((http::read_body(client, headers) >> file).empty()) {
                throw http::bad_request_error();
            }

            string hosts;

            {
                lock_guard<mutex> resources_guard(resources_mutex);
                for(auto& host : network.get_hosts(file)) {
                    hosts.append(host).append(crlf);
                }
            }

            client <<
                "HTTP/1.1 200 OK" << crlf <<
                "Connection: Keep-Alive" << crlf <<
                "Content-Length: " << hosts.length() << crlf <<
                "" << crlf <<
                hosts <<
                ostream::flush;
        };

        list<thread> threads;
        list<sock_inet_tcp> clients;

        while(true) {
            clients.emplace_back(server.accept());

            threads.push_back(thread([&](sock_inet_tcp& client) {
                while(true) {
                    try {
                        try {
                            auto location = http::read_request(client);

                            if(locations.find(location) == locations.end()) {
                                throw http::not_found_error();
                            }

                            locations[location](ref(client), http::read_headers(client));
                        }
                        catch(http::error& e) {
                            client << e.what() << crlf << crlf;
                            throw stream_closed_exception();
                        }
                    }
                    catch(stream_closed_exception& e) {
                        lock_guard<mutex> resources_guard(resources_mutex);

                        auto peer = peers.find(client.get_fd());

                        if(peer != peers.end()) {
                            network.erase(peer->second);
                            peers.erase(peer);
                        }

                        break;
                    }
                }
            }, ref(clients.back())));
        }
    }
    catch(exception& e) {
        cerr << e.what() << endl;
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
